package bootstrap

import (
	"context"
	"database/sql"
	"fmt"
	"math/rand"
	"net"
	"net/url"
	"time"

	"gddgame.cc/galaxy"
	_ "gddgame.cc/galaxy/common/feature/phantom/aliyun"
	redisCache "gddgame.cc/galaxy/utils/cache/redis"
	"gddgame.cc/galaxy/utils/def"
	"gddgame.cc/galaxy/utils/orm"
	"gddgame.cc/galaxy/utils/serialize/msgpack"

	_ "github.com/go-sql-driver/mysql"
	"github.com/gomodule/redigo/redis"
	"github.com/pkg/errors"
)

type Boot struct {
	required []string

	expire   map[string]interface{}
	redis    *redis.Pool
	cache    *redisCache.Cache
	db       *sql.DB
	executor orm.Executor

	logger def.Logger
	ctx    context.Context
}

var (
	DefaultBoot = &Boot{required: []string{"db", "redis"}}
	SmallBoot   = &Boot{required: []string{"redis"}}
	ZeroBoot    = &Boot{required: []string{}}
)

func (b *Boot) ParseConfig(ctx context.Context, logger def.Logger, config *galaxy.Config) ([]interface{}, error) {
	b.logger = logger
	b.ctx = ctx
	for _, key := range b.required {
		if !config.Exist(key) {
			return nil, errors.New("config miss: " + key)
		}
	}
	expire := map[string]interface{}{}
	if err := config.Bind("expire", &expire, false); err != nil {
		return nil, err
	}

	if dsn := config.Read("db"); dsn != nil {
		db, err := b.loadMysql(dsn.(string))
		if err != nil {
			return nil, err
		}
		b.db = db
		executor := orm.DefaultExecutor()
		executor.ConnWithDriver(db, orm.MYSQL)
		executor.SetLogger(logger.LoggerWrap("Ext", "orm"))
		b.executor = executor
	}

	if dsn := config.Read("redis"); dsn != nil {
		r, err := b.loadRedis(dsn.(string))
		if err != nil {
			return nil, err
		}
		// 使用msgpack作为序列化
		s := msgpack.Msgpack(false)
		b.redis = r
		b.cache = redisCache.Redis(b.logger, b.redis, "boot", s)
	}

	// 设定随机
	rand.Seed(time.Now().UnixNano())
	return []interface{}{b, b.db, b.redis}, nil
}

func (b *Boot) loadMysql(dsn string) (*sql.DB, error) {
	b.logger.Debugf("load mysql config")
	u, err := url.Parse(dsn)
	if err != nil {
		return nil, err
	}
	host, port, err := net.SplitHostPort(u.Host)
	if err != nil {
		host = u.Host
	}
	if host == "" {
		return nil, fmt.Errorf("MysqlServer host is empty: %s", u)
	}
	if port == "" {
		port = "3306"
	}
	b.logger.Debugf("MysqlServer info: host->%#v, port->%#v", host, port)
	db, err := sql.Open(u.Scheme, fmt.Sprintf("%s@tcp(%s)%s?charset=utf8", u.User, u.Host, u.Path))

	if err == nil {
		db.SetConnMaxLifetime(time.Minute)
	}
	return db, err
}

func (b *Boot) loadRedis(dsn string) (*redis.Pool, error) {
	b.logger.Debugf("load redis config")
	u, err := url.Parse(dsn)
	if err != nil {
		return nil, err
	}
	host, port, err := net.SplitHostPort(u.Host)
	if err != nil {
		host = u.Host
	}
	if host == "" {
		return nil, fmt.Errorf("RedisServer host is empty: %s", u)
	}
	if port == "" {
		port = "6379"
	}
	b.logger.Debugf("RedisServer info: host->%#v, port->%#v", host, port)
	address := net.JoinHostPort(host, port)
	options := []redis.DialOption{}
	if u.User != nil {
		password, isSet := u.User.Password()
		if isSet {
			options = append(options, redis.DialPassword(password))
		}
	}

	pool := redis.Pool{
		MaxIdle:     30,
		IdleTimeout: 240 * time.Second,
		Dial: func() (redis.Conn, error) {
			return redis.Dial("tcp", address, options...)
		},
	}
	return &pool, err
}
